package com.tlgen.tomcat.utils;

import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.tlgen.tomcat.annotation.*;
import com.tlgen.tomcat.model.FilterClasses;
import com.tlgen.tomcat.model.RequestMappingClasses;
import com.tlgen.tomcat.model.RequestMethodAndUrl;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.tlgen.tomcat.utils.ScanUtils.getClassesMethodWithAnnotation;
import static com.tlgen.tomcat.utils.ScanUtils.getClassesWithAnnotation;

public class AnnotationUtils {

    /**
     * 获取包含 @RequestMapping 注解的类集合
     *
     * @return
     */
    public static List<RequestMappingClasses> getRequestMappingClassesWithAnnotation() {
        List<String> classesWithAnnotations = Lists.newArrayList();
        classesWithAnnotations.addAll(getClassesWithAnnotation(RequestMapping.class));
        classesWithAnnotations.addAll(getClassesMethodWithAnnotation(GetMapping.class));
        classesWithAnnotations.addAll(getClassesMethodWithAnnotation(PostMapping.class));
        classesWithAnnotations.addAll(getClassesMethodWithAnnotation(PutMapping.class));
        classesWithAnnotations.addAll(getClassesMethodWithAnnotation(DeleteMapping.class));
        classesWithAnnotations = classesWithAnnotations.stream().distinct().collect(Collectors.toList());
        List<RequestMappingClasses> classesList = Lists.newArrayList();
        for (String className : classesWithAnnotations) {
            classesList.add(RequestMappingClasses.builder()
                    .className(className)
                    .annotationValue(getRequestMappingClassAnnotationValue(className))
                    .requestMethodAndUrls(getRequestMappingClassMethodUrls(className))
                    .build());
        }
        return classesList;
    }

    /**
     * 获取包含 @Filter 注解的类集合
     *
     * @return
     */
    public static List<FilterClasses> getFilterClassesWithAnnotation() {
        List<String> classesWithAnnotation = getClassesWithAnnotation(Filter.class);
        List<FilterClasses> classesList = Lists.newArrayList();
        for (String className : classesWithAnnotation) {
            classesList.add(FilterClasses.builder()
                    .className(className)
                    .build());
        }
        return classesList;
    }

    /**
     * 获取 @RequestMapping 注解的 value 值
     *
     * @param className
     * @return
     */
    public static String getRequestMappingClassAnnotationValue(String className) {
        String value = "";
        try {
            Class<?> aClass = Class.forName(className);
            RequestMapping annotation = aClass.getAnnotation(RequestMapping.class);
            if (Objects.nonNull(annotation))
                value = annotation.value();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return value;
    }

    /**
     * 获取 @RequestMapping 标注的类中的 method 和 url 集合
     *
     * @param className
     * @return
     */
    public static List<RequestMethodAndUrl> getRequestMappingClassMethodUrls(String className) {
        String parentPath = getRequestMappingClassAnnotationValue(className);
        List<RequestMethodAndUrl> requestMethodAndUrlList = new ArrayList<>();
        try {
            Class<?> aClass = Class.forName(className);
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                RequestMethodAndUrl requestMethodAndUrl = new RequestMethodAndUrl();
                requestMethodAndUrl.setClassName(className);
                requestMethodAndUrl.setMethodName(declaredMethod.getName());
                GetMapping getMapping = declaredMethod.getAnnotation(GetMapping.class);
                Optional.ofNullable(getMapping).ifPresent(c -> {
                    if (StrUtil.isNotBlank(getMapping.value())) {
                        requestMethodAndUrl.setRequestType("GET");
                        requestMethodAndUrl.setUrl(parentPath + getMapping.value());
                    }
                });
                PostMapping postMapping = declaredMethod.getAnnotation(PostMapping.class);
                Optional.ofNullable(postMapping).ifPresent(c -> {
                    if (StrUtil.isNotBlank(c.value())) {
                        requestMethodAndUrl.setRequestType("POST");
                        requestMethodAndUrl.setUrl(parentPath + postMapping.value());
                        setRequestMethodUrlBodyType(declaredMethod, requestMethodAndUrl, false);
                    }
                });
                PutMapping putMapping = declaredMethod.getAnnotation(PutMapping.class);
                Optional.ofNullable(putMapping).ifPresent(c -> {
                    if (StrUtil.isNotBlank(putMapping.value())) {
                        requestMethodAndUrl.setRequestType("PUT");
                        requestMethodAndUrl.setUrl(parentPath + putMapping.value());
                        setRequestMethodUrlBodyType(declaredMethod, requestMethodAndUrl, false);
                    }
                });
                DeleteMapping deleteMapping = declaredMethod.getAnnotation(DeleteMapping.class);
                Optional.ofNullable(deleteMapping).ifPresent(c -> {
                    if (StrUtil.isNotBlank(deleteMapping.value())) {
                        requestMethodAndUrl.setRequestType("DELETE");
                        requestMethodAndUrl.setUrl(parentPath + deleteMapping.value());
                        setRequestMethodUrlBodyType(declaredMethod, requestMethodAndUrl, true);
                    }
                });
                requestMethodAndUrlList.add(requestMethodAndUrl);
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return requestMethodAndUrlList.stream().filter(x -> StrUtil.isNotBlank(x.getUrl())).collect(Collectors.toList());
    }

    private static void setRequestMethodUrlBodyType(Method declaredMethod, RequestMethodAndUrl requestMethodAndUrl,
                                                    Boolean isDelete) {
        Parameter[] parameters = declaredMethod.getParameters();
        Parameter parameter = parameters[0];
        RequestBody requestBodyAnnotation = parameter.getAnnotation(RequestBody.class);
        RequestParam requestParamAnnotation = parameter.getAnnotation(RequestParam.class);
        if (Objects.nonNull(requestBodyAnnotation)) {
            requestMethodAndUrl.setBodyType(0);
        } else if (Objects.nonNull(requestParamAnnotation)) {
            requestMethodAndUrl.setBodyType(1);
        } else {
            if (isDelete) {
                PathVariable pathVariableAnnotation = parameter.getAnnotation(PathVariable.class);
                if (Objects.nonNull(pathVariableAnnotation)) {
                    requestMethodAndUrl.setBodyType(3);
                }
            } else {
                requestMethodAndUrl.setBodyType(2);
            }
        }
    }

}
